#include "ClientHandler.h"
#include "ScreenHandler.h"
#include "WorldHandler.h"
#include "Logging.h"
#include "NetworkPacket.h"
#include "Config.h"
#include "DBCHandler.h"

#include <cstdlib>


#include <sstream>

#ifdef _WIN32
	#include <windows.h>
#else
	#include <unistd.h>
#endif

#define TIMEOUT_MAX 5

ClientHandler::ClientHandler()
{
	
}

void ClientHandler::handleReceivedMessages()
{
	for(size_t i = 0; i < receivedQueue.size(); ++i)
	{
		NetworkPacket* packet = &receivedQueue.at(i).first; 
//		ClientAddress curCliAddr = receivedQueue.at(i).second;

		switch(packet->first) //HEADER
		{
			case ACTIVATECOMPASS:
				logMessage("Received ACTIVATECOMPASS from server.");
				globalWorldHandler.setIsCompassObjectActive(true);
			break;
			case INACTIVATECOMPASS:
				logMessage("Received INACTIVATECOMPASS from server.");
				globalWorldHandler.setIsCompassObjectActive(false);
			break;
			case CHATLOG:
				logMessage("Received CHATLOG from server.");
				handleReceivedChatLog(packet->second);
			break;
			case PLAYEROBJECTVECTOR:
			//	logMessage("Received PLAYEROBJECTVECTOR from server.");
				handleReceivedPlayerObjectVector(packet->second);
			break;
			case ATTACKOBJECTVECTOR:
		//		logMessage("Received ATTACKOBJECTVECTOR from server.");
				handleReceivedAttackObjectVector(packet->second);
			break;
			case ENEMYOBJECTVECTOR:
				//logMessage("Received ENEMYOBJECTVECTOR from server.");
				handleReceivedEnemyObjectVector(packet->second);
			break;
			
#if 0
			case PLAYERINDEX:
				logMessage("Received PLAYERINDEX from server.");
				handleReceivedPlayerIndex(packet.second);
			break;
#endif
			case GRANTSEEDANDTP:
				logMessage("Received GRANTSEEDANDTP from server.");
				handleReceivedGrantSeedAndTP(packet->second);
			break;
			case GRANTDISMANTLE:
				logMessage("Received GRANTDISMANTLE from server.");
				handleReceivedGrantDismantle();
			break;
			case DISC:
				logMessage("Received DISC from server.");
				handleReceivedDisc(packet->second);
			break;
			case SHUTDOWN:
				logMessage("Received SHUTDOWN from server.");
				globalScreenHandler.appendToChatLog("[SERVER]: Server shutdown.");
			break;
		}
	}

	if(receivedQueue.size() > 0)
	{
		globalCSHandler->setHasConnection(true);
	}

	receivedQueue.clear();
}

void ClientHandler::handleReceivedDisc(const std::string& name)
{
	std::map<std::string, PlayerObject*>::iterator it = clientPlayerObjectMap.find(name);
	
	if(it != clientPlayerObjectMap.end())
	{
		PlayerObject* playerObject = static_cast<PlayerObject*>(it->second);

		playerObject->setStartMoving(false);
		playerObject->setIsMoving(false);
		reinterpret_cast<scene::ISceneNode*>(playerObject->getNode())->setVisible(false);

		playerObject->setMovementInfo(playerObject->getPosition(), core::vector3df(-1, -1, -1), core::vector3df(0, 0, 0), 0, 0);
		playerObject->setIsConnected(false);
	}
}

void ClientHandler::handleReceivedChatLog(const std::string& chatLog)
{
	if(globalScreenHandler.getChatLog() != chatLog)
	{
		globalScreenHandler.setChatLog(chatLog); 
	}
}

void ClientHandler::handleReceivedPlayerObjectVector(const std::string& message)
{
	std::vector<PlayerObject> newPlayerObjects; 

	if(serializer.callDeserialize(reinterpret_cast<uintptr_t>(&newPlayerObjects), PLAYEROBJECTVECTOR, message) == false)
	{
		return;
	}

	size_t playerSize = newPlayerObjects.size();

	PlayerObject newPlayerObject;

//	globalWorldHandler.setLocalPlayerIndex(1);
	
	for(int curID = globalWorldHandler.getPlayerCount(); globalWorldHandler.getPlayerCount() < playerSize; ++curID)
	{
		PlayerObject* newPlayerObject = &newPlayerObjects.at(curID); // vad vad //vad
		MovementInfo* newMovementInfo = newPlayerObject->getMovementInfo();

		std::string name = newPlayerObject->name;		
		PlayerObject* playerObject = globalWorldHandler.setupNewPlayer(name, newMovementInfo->startPos);

		if(name == globalConfig.getLocalPlayerName()) 
		{
			globalWorldHandler.setLocalPlayerIndex(curID);
		}
		
		clientPlayerObjectMap.insert(std::pair<std::string, PlayerObject*>(name, playerObject));;
	}
	
	std::vector<PlayerObject*> oldPlayerObjects = globalWorldHandler.getPlayerObjects(); 

	unsigned int curPlayerObj = 0;

	do
	{
		PlayerObject* playerObject = oldPlayerObjects[curPlayerObj];
		PlayerObject newPlayerObject = newPlayerObjects[curPlayerObj];

		if(playerObject->getIsDead() == true
		&& newPlayerObject.isDead == false)
		{
			playerObject->setAnimation(scene::EMAT_STAND, true);
		}
		
		playerObject->setIsDead(newPlayerObject.isDead);
		playerObject->setKillCount(newPlayerObject.killCount);
		
		if(playerObject->getName() != globalConfig.getLocalPlayerName())
		{
			MovementInfo* oldMovementInfo = playerObject->getMovementInfo();
			MovementInfo* newMovementInfo = newPlayerObject.getMovementInfo();

			core::matrix4 mat = newPlayerObject.mat;
			
			core::vector3df oldPosition = playerObject->getPosition();
			core::vector3df newPosition = newPlayerObject.getPosition();		
			
			core::vector3df distance = newPosition - oldPosition;
			if(sqrt(distance.X*distance.X) > 20
			|| sqrt(distance.Z*distance.Z) > 20
			&& !(sqrt(distance.X*distance.X) > 2500 || sqrt(distance.Z*distance.Z) > 2500))
			{
				playerObject->setPosition(newPosition);
				newMovementInfo->startPos = newPosition;
				playerObject->setIsMoving(false);
				playerObject->setStartMoving(false);
			}
			
			if(oldMovementInfo->destinationPos != newMovementInfo->destinationPos)
			{
				playerObject->faceTarget(playerObject->getPosition() + core::vector3df(mat[0],mat[1],mat[2]));		
				
				if(oldMovementInfo->destinationPos == core::vector3df(-1, -1, -1))
				{
					scene::ISceneNode* node = reinterpret_cast<scene::ISceneNode*>(playerObject->getNode());
					if(node != NULL)
					{
						node->setVisible(true);
						playerObject->setIsConnected(true);
					}
					playerObject->setMovementInfo(newMovementInfo->startPos, core::vector3df(0, 0, 0), core::vector3df(0, 0, 0), 0, 0); 
				}
				else if(newMovementInfo->destinationPos != core::vector3df(0, 0, 0)
				&& !(sqrt(distance.X*distance.X) > 2500 || sqrt(distance.Z*distance.Z) > 2500))
				{
					playerObject->setMovementInfo(newMovementInfo->startPos, newMovementInfo->destinationPos, core::vector3df(0, 0, 0), 0, 0); 
					playerObject->setStartMoving(true);
				}
			}
		}
		++curPlayerObj;
	}
	while(curPlayerObj < playerSize);
}

void ClientHandler::prepareSendQueue()
{
	if(globalWorldHandler.getLocalPlayerIndex() != 0)
	{
		std::vector<NetworkPacket> packetBundle;

		std::vector<PlayerObject*>playerObjects = globalWorldHandler.getPlayerObjects();
		PlayerObject playerObject = *playerObjects[globalWorldHandler.getLocalPlayerIndex()];
		packetBundle.push_back(std::make_pair(PLAYEROBJECT, serializer.callSerialize(reinterpret_cast<uintptr_t>(&playerObject), PLAYEROBJECT)));

		sendQueue.insert(sendQueue.end(), packetBundle.begin(), packetBundle.end());
	}
}

void ClientHandler::sendMessages()
{
	if(sendQueue.size() > 0)
	{
		socketCommunicator.sendSync(serializer.callSerialize(reinterpret_cast<uintptr_t>(&sendQueue), PACKETBUNDLE));
	}
}

void ClientHandler::handleReceivedGrantSeedAndTP(const std::string& seedString)
{
	if(globalWorldHandler.getRandomSeed() == NO_SEED
	|| globalWorldHandler.getRandomSeed() == WANT_SERVERSEED)
	{
		unsigned int seed;
		std::stringstream ss;
		ss << seedString;
		ss >> seed;

		if(globalWorldHandler.getActiveRandomZoneObject() == NULL)
		{
			globalWorldHandler.setRandomSeed(seed); // +2 to skip 0[NO_SEED] and 1[WANT_SERVERSEED]
			globalWorldHandler.setupRandomZone();
		}
		else
		{
			globalWorldHandler.dismantleRandomZone();
		}
	}
}

void ClientHandler::handleReceivedGrantDismantle()
{
	globalWorldHandler.dismantleRandomZone();
	globalWorldHandler.setRandomSeed(0);
}

void ClientHandler::handleReceivedAttackObjectVector(const std::string& message)
{
	std::vector<AttackObject> newAttackObjects; 

	if(serializer.callDeserialize(reinterpret_cast<uintptr_t>(&newAttackObjects), ATTACKOBJECTVECTOR, message) == false)
	{
		return;
	}

	std::map<int, AttackObject*> oldAttackIDMap = globalWorldHandler.getAttackIDMap();
	std::map<int, AttackObject*> newAttackIDMap;

	std::string localPlayerName = globalConfig.getLocalPlayerName();
	
	
	for(size_t i = 0; i < newAttackObjects.size(); ++i)
	{
		int ID = newAttackObjects[i].ID;
		
		newAttackIDMap.insert(std::make_pair<int, AttackObject*>(ID, NULL)); //NULL because newAttackIDMap is used only in this function, only for find()
		std::string name = newAttackObjects[i].originatingPlayer;
		//&& clientPlayerObjectMap.find(newAttackObjects[i].originatingPlayer) != clientPlayerObjectMap.end()
		if(oldAttackIDMap.find(ID) == oldAttackIDMap.end() //projectile was not found locally
		&& name != localPlayerName)
		{
			std::map<std::string, PlayerObject*>::iterator it = clientPlayerObjectMap.find(name);
			if(it != clientPlayerObjectMap.end())
			{
				PlayerObject* playerObject = static_cast<PlayerObject*>(it->second);
			
				core::vector3df startPos = newAttackObjects[i].position;
				core::matrix4 mat = newAttackObjects[i].mat;
				AttackType type = newAttackObjects[i].attackType;
				playerObject->faceTarget(playerObject->getPosition() + core::vector3df(mat[0],mat[1],mat[2]));
				playerObject->setAnimation(scene::EMAT_ATTACK, false);
				globalWorldHandler.setupAttack(playerObject->getPosition(), mat, type, true, name, ID);
			}
		}
	}
	
	std::vector<AttackObject*> oldAttackObjects = globalWorldHandler.getAttackObjects(); 
	for(size_t i = 0; i < oldAttackObjects.size(); ++i)
	{
		std::string name = oldAttackObjects.at(i)->getOriginatingPlayer();
		if(name != localPlayerName
		&& newAttackIDMap.find(oldAttackObjects.at(i)->getID()) == newAttackIDMap.end()) // a local projectile no longer exists server side
		{
			globalWorldHandler.removeWorldObject(oldAttackObjects.at(i));
		}
	}

	//TODO: support for removing projectiles when server says to do so
}

void ClientHandler::handleReceivedEnemyObjectVector(const std::string& message)
{
	std::vector<EnemyObject> newEnemyObjects;
	
	if(serializer.callDeserialize(reinterpret_cast<uintptr_t>(&newEnemyObjects), ENEMYOBJECTVECTOR, message) == false)
	{
		return;
	}
	
	std::map<int, EnemyObject*> oldEnemyIDMap = globalWorldHandler.getEnemyIDMap();
	std::map<int, EnemyObject*> newEnemyIDMap;
	
	
	for(size_t i = 0; i < newEnemyObjects.size(); ++i)
	{
		int ID = newEnemyObjects[i].ID;
		
		newEnemyIDMap.insert(std::make_pair<int, EnemyObject*>(ID, NULL));
		std::map<int, EnemyObject*>::iterator it = oldEnemyIDMap.find(ID);
		
		EnemyObject* enemy;
		
		if(it == oldEnemyIDMap.end()) // enemy was not found locally
		{
			int key = newEnemyObjects[i].enemyMapKey;
			core::vector3df pos(newEnemyObjects[i].position);
			enemy = globalDBCHandler.createEnemy(key, pos, ID);
		}
		else
		{
			enemy = static_cast<EnemyObject*>(it->second);	
		}
		
		if(newEnemyObjects[i].isDead == false)
		{
			MovementInfo* newMovementInfo = newEnemyObjects[i].getMovementInfo();
			enemy->setPosition(newEnemyObjects[i].position);
			enemy->setMovementInfo(newEnemyObjects[i].position, newMovementInfo->destinationPos, core::vector3df(0, 0, 0), 0, 0);
			enemy->setStartMoving(true);
		}
		
		enemy->setIsDead(newEnemyObjects[i].isDead);
	}
	
	//TODO: remove enemies that are not on server
}
